﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;

public class MyTcpClientConn
{
    public enum ConnectStatus
    {
        Disconnect,
        Begin,
        Connected,
    }

    private const bool Log_Enable = false;

    private Queue<SocketHelper.ThreadMessage> m_ThreadMessageQueue;
    private IPEndPoint m_ServerIpPoint;

    private Socket m_Socket;
    private bool m_IsStopping;

    private ConnectStatus m_ConnectStatus = ConnectStatus.Disconnect; //1)渲染线程写然后启动Recv线程, 2)Recv线程写, 渲染线程读
    private string m_DisconnectReason = "";
    private int m_ConnectNum; //成功连接前的已连接次数
    private long m_LastRecvTime;
    private Thread m_RecvThread;

    private Queue<string> m_SendQueue = new Queue<string>();
    private Thread m_SendThread; //1)渲染线程写然后启动Send线程, 2)Send线程写null前, 渲染线程读到了


    public MyTcpClientConn(Queue<SocketHelper.ThreadMessage> msgQueue)
    {
        if (Log_Enable) Debug.Log($"MyTcpClientConn.ctor");
        m_ThreadMessageQueue = msgQueue;
    }

    //# 连接成功后, 无法主动Disconnect, 因为要Socket.Close才能唤醒阻塞的Receive, 这样Socket也就无法再重用了;
    //# 连接成功后, 被服务器断开连接引起的Disconnect: 那发送线程就持有了一个无效的socket
    //# 没连接成功前, 可以重复重连

    public void SetServerAddress(IPEndPoint endPoint)
    {
        m_ServerIpPoint = endPoint;
    }
    
    //渲染线程上调用的接口的, 调用时机:
    //1) 一次还没连接成功过
    //2) 连接成功了, 然后又断开了, 收到Client.Disconnect消息后, 再调用(即RecvThread退出后)

    //连接服务器
    public void ConnectServer()
    {
        if (null != m_RecvThread || null != m_SendThread) return;

        //还没连: 新建Socket
        //没连上: 用上次的Socket
        //连上后断开了: 新建Socket
        if (null != m_Socket)
        {
            //None, Disconnect, 且SendThread不再运行时, 才可再连接服务器(再复用该对象, 这个对象是ThreadState?)
            //不然线程不知道啥时候结束呢
            if (ConnectStatus.Begin == m_ConnectStatus || ConnectStatus.Connected == m_ConnectStatus)
            {
                if (Log_Enable) Debug.Log($"client: Connecting or Connected");
                return;
            }

            if ("Disconnect" == m_DisconnectReason)
            {
                if (Log_Enable) Debug.Log($"client: Disconnect, ShutdownAndClose");
                SocketHelper.ShutdownAndClose(m_Socket);
                m_Socket = null;
            }
        }
        
        if (null == m_Socket)
        {
            if (Log_Enable) Debug.Log($"client: new Socket");
            //var ipAddress = IPAddress.Parse("127.0.0.1");
            //var ipPoint = new IPEndPoint(ipAddress, 8081);
            //m_ServerIpPoint = ipPoint;

            m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            m_Socket.NoDelay = true;
            Debug.Log($"client: recvBuffSize:{m_Socket.ReceiveBufferSize}, sendBuffSize:{m_Socket.SendBufferSize}, sendTimeout:{m_Socket.SendTimeout}, recvTimeout:{m_Socket.ReceiveTimeout}");
            //m_Socket.ReceiveBufferSize = 64 * 1024;
            //m_Socket.SendBufferSize = 64 * 1024;
            //m_Socket.SendTimeout = 15;
            //m_Socket.ReceiveTimeout = 15;
        }

        if (m_IsStopping)
        {
            m_IsStopping = false;

            m_ConnectStatus = ConnectStatus.Begin;
            m_DisconnectReason = "";
            m_ConnectNum = 0;
            m_LastRecvTime = SocketHelper.CurrentTimeMillis();

            Debug.Log($"client: sendQueue:{m_SendQueue.Count}");
            m_SendQueue.Clear();
        }

        m_RecvThread = new Thread(OnReceiveThread);
        m_RecvThread.Start(m_Socket);
    }

    public ConnectStatus connectStatus
    {
        //Disconnect时, 再检查是否IsStopped
        get { return m_ConnectStatus; }
    }

    public long LastRecvTime
    {
        get { return m_LastRecvTime; }
    }

    public bool IsStopped //还没连, 没连上, 连上后断开了
    {
        get { return (null == m_RecvThread && null == m_SendThread); }
    }

    public void Stop()
    {
        if (null == m_Socket) return; //还没连
        if (m_IsStopping) return;
        m_IsStopping = true;

        var t = m_RecvThread; //Connecting中Stop?
        if (null != t)
            t.Interrupt();

        t = m_SendThread;
        if (null != t)
        {
            lock (m_SendQueue)
            {
                if (m_SendQueue.Count < 0)
                    Monitor.PulseAll(m_SendQueue); //唤醒阻塞中的发送线程
            }

            t.Interrupt();
        }

        SocketHelper.ShutdownAndClose(m_Socket);
        m_Socket = null;
    }

    public void Send(string str)
    {
        if (null == m_Socket) return;
        if (ConnectStatus.Connected != m_ConnectStatus) return;

        if (null == m_SendThread)
        {
            m_SendThread = new Thread(OnSendThread);
            m_SendThread.Start(m_Socket);
        }

        lock (m_SendQueue)
        {
            m_SendQueue.Enqueue(str);
            Monitor.Pulse(m_SendQueue); //唤醒一个线程
        }
    }

    //******************** 异步线程

    private void OnReceiveThread(object obj)
    {
        SocketHelper.ThreadMessage msg;

        var time1 = DateTime.Now;
        var socket = (Socket)obj;
        try
        {
            ++m_ConnectNum;
            if (Log_Enable) Debug.Log($"client: BeginConnect: connected:{socket.Connected}, connectNum:{m_ConnectNum}");
            //如何重用Socket?
            //if (socket.Connected)
            //    socket.Disconnect(true);
            socket.Connect(m_ServerIpPoint);
        }
        catch (Exception ex)
        {
            Debug.LogWarning($"client: connected:{socket.Connected}, {ex.GetType().Name}: {ex.Message}");
            m_ConnectStatus = ConnectStatus.Disconnect;
            m_DisconnectReason = "Fail";

            m_RecvThread = null;
            msg = new SocketHelper.ThreadMessage();
            msg.m_Cmd = "Client.ConnectFail";
            //msg.m_obj = ex.Message;
            EnqueueMessage(msg);
            return;
        }
        m_ConnectNum = 0;

        if (Log_Enable) Debug.Log($"client: connected:{socket.Connected}, local:{socket.LocalEndPoint}, remote:{socket.RemoteEndPoint}");
        //var elapseTime = DateTime.Now - time1;
        //if (elapseTime.TotalMilliseconds >= 5000)
        //{
        //    //todo: 连接超时
        //}

        m_ConnectStatus = ConnectStatus.Connected;
        msg = new SocketHelper.ThreadMessage();
        msg.m_Cmd = "Client.Connected";
        EnqueueMessage(msg);

        RecvLoop(socket);

        m_DisconnectReason = "Disconnect";
        m_ConnectStatus = ConnectStatus.Disconnect;

        //断开了, conn丢掉
        msg = new SocketHelper.ThreadMessage();
        msg.m_Cmd = "Client.Disconnect";
        EnqueueMessage(msg);

        m_RecvThread = null;
        Debug.Log($"client: RecvThread exit: connected:{socket.Connected}, tid:{Thread.CurrentThread.ManagedThreadId}");
    }

    private void RecvLoop(Socket socket)
    {
        SocketHelper.ThreadMessage msg;

        byte[] buff = new byte[8 * 1024];
        int notReadLen = 0;
        //渲染线程正在写true前, 这边读到了false
        while (!m_IsStopping)
        {
            try
            {
                int buffRemainLen = buff.Length - notReadLen;
                if (buffRemainLen <= 0)
                {
                    Debug.LogError($"client: RecvThread: buffRemainBytes <= 0, local:{socket.LocalEndPoint}, remote:{socket.RemoteEndPoint}");
                    break; //视为断开
                }

                if (Log_Enable) Debug.Log($"client: Receive1: offset:{notReadLen}, ct:{buffRemainLen}, local:{socket.LocalEndPoint}, remote:{socket.RemoteEndPoint}");
                int recvLen = socket.Receive(buff, notReadLen, buffRemainLen, 0, out var errCode); //Thread.Interrupt无法中断阻塞
                if (0 == recvLen) //FIN
                {
                    //todo: 中断连接, 比如: 服务器调用Shutdown
                    if (Log_Enable) Debug.Log($"client: RecvThread: FIN: connected:{socket.Connected}, local:{socket.LocalEndPoint}, remote:{socket.RemoteEndPoint}");
                    break;
                }

                m_LastRecvTime = DateTime.Now.Ticks;
                notReadLen += recvLen;
                int readedLen = 0;
                while (SocketHelper.IsPacketReady(buff, readedLen, notReadLen, out var bodyLen))
                {
                    var str = Encoding.UTF8.GetString(buff, readedLen + SocketHelper.Packet_Head_Len, bodyLen);
                    if (Log_Enable) Debug.Log($"client: str:{str}, tid:{Thread.CurrentThread.ManagedThreadId}");
                    msg = new SocketHelper.ThreadMessage();
                    msg.m_Cmd = "Client.ServerPacket";
                    msg.m_Content = str;
                    EnqueueMessage(msg);

                    notReadLen -= SocketHelper.Packet_Head_Len;
                    notReadLen -= bodyLen;

                    readedLen += SocketHelper.Packet_Head_Len;
                    readedLen += bodyLen;
                }

                if (notReadLen > 0)
                {
                    if (Log_Enable) Debug.Log($"client: RecvThread: nextPacketBytes:{readedLen}~{readedLen + notReadLen}, local:{socket.LocalEndPoint}, remote:{socket.RemoteEndPoint}");
                    Buffer.BlockCopy(buff, readedLen, buff, 0, notReadLen);
                }
            }
            catch (ObjectDisposedException ex)
            {
                Debug.LogWarning($"client: RecvThread: connected:{socket.Connected}, {ex.GetType().Name}: {ex.Message}");
            }
            catch (Exception ex)
            {
                Debug.LogError($"client: RecvThread: connected:{socket.Connected}, {ex.GetType().Name}: {ex.Message}");
            }
        }
    }

    private void OnSendThread(object obj)
    {
        SendLoop((Socket)obj);

        m_SendThread = null; //异步线程写null前, 渲染线程读到有值, 就没创建线程, 然后放入发送队列, 实际没值了
        Debug.Log($"client: SendThread exit: tid:{Thread.CurrentThread.ManagedThreadId}");
    }

    private void SendLoop(Socket socket)
    {
        string str = "";
        while (!m_IsStopping && socket.Connected)
        {
            try
            {
                lock (m_SendQueue)
                {
                    while (m_SendQueue.Count <= 0)
                    {
                        if (Log_Enable) Debug.Log($"client: wait SendQueue1");
                        Monitor.Wait(m_SendQueue); //Thread.Interrupt可以中断

                        if (m_IsStopping) return;
                    }

                    str = m_SendQueue.Dequeue();
                }
            }
            catch (ThreadInterruptedException ex)
            {
                Debug.LogError($"client: SendThread: connected:{socket.Connected}, interrupted: {ex.Message}");
                break;
            }

            try
            {
                var strBytes = Encoding.UTF8.GetBytes(str);
                int bodyLen = strBytes.Length;
                var sendBytes = new byte[SocketHelper.Packet_Head_Len + bodyLen]; //todo: 复用

                SocketHelper.ConvertInt32To4Bytes(bodyLen, sendBytes, 0); //写入消息头
                Buffer.BlockCopy(strBytes, 0, sendBytes, SocketHelper.Packet_Head_Len, bodyLen); //copy消息体

                if (m_IsStopping) return;
                int sendLen = 0;
                do
                {
                    int sendRemainLen = sendBytes.Length - sendLen;
                    if (Log_Enable) Debug.Log($"client: send1: {socket.Connected}");
                    int tempSendLen = socket.Send(sendBytes, sendLen, sendRemainLen, 0);
                    if (tempSendLen <= 0) //服务器断开了?
                    {
                        Debug.LogError($"client: sendLen <= 0: local:{socket.LocalEndPoint}, remote:{socket.RemoteEndPoint}");
                        break;
                    }
                    sendLen += tempSendLen;
                } while (sendLen < sendBytes.Length);
                if (Log_Enable) Debug.Log($"client: sendEnd: local:{socket.LocalEndPoint}, remote:{socket.RemoteEndPoint}");
            }
            catch (Exception ex)
            {
                Debug.LogError($"client: SendThread: connected:{socket.Connected}, {ex.GetType().Name}: {ex.Message}");
            }
        }
    }
    

    private void EnqueueMessage(SocketHelper.ThreadMessage msg)
    {
        if (m_IsStopping) //渲染线程正在写true前, 这边读到了false: 则渲染线程后续会读到失效连接的消息, 要紧吗?
        {
            Debug.Log($"client: stopping, Message discard: {msg.m_Cmd}");
            return;
        }

        lock (m_ThreadMessageQueue)
        {
            m_ThreadMessageQueue.Enqueue(msg);
        }
    }

    //********************

}
